﻿using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Storage;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using ZFY.Core;
using ZFY.UnitOfWork.Interface;

namespace ZFY.UnitOfWork
{
    public class ZFYUnitOfWork : IZFYUnitOfWork, IScopeDependency
    {
  

        public Guid Id { get; }
        private Exception _exception;
        private bool _isCompleting;
        private bool _isRolledback;
        public UnitOfWorkOption Options { get; private set; }
        public IZFYUnitOfWork Outer { get; private set; }
        public bool IsDisposed { get; private set; }
        public bool IsCompleted { get; private set; }
        private readonly Dictionary<string, IDbContextApi> _dbContexts;
        private readonly Dictionary<string, ITransactionApi> _transactions;
        protected List<Func<Task>> CompletedHandlers { get; } = new List<Func<Task>>();
        public IServiceProvider ServiceProvider { get; }
        public event EventHandler<UnitOfWorkFailedEventArgs> Failed;
        public event EventHandler<UnitOfWorkEventArgs> Disposed;
        public ZFYUnitOfWork(IServiceProvider serviceProvider)
        {
            Id = Guid.NewGuid();
            ServiceProvider = serviceProvider;
            _dbContexts = new Dictionary<string, IDbContextApi>();
            _transactions = new Dictionary<string, ITransactionApi>();
        }
        public async Task CompleteAsync(CancellationToken cancellationToken = default)
        {
            
            if (_isRolledback)
            {
                return;
            }
            PreventMultipleComplete();

            try
            {
                _isCompleting = true;
                await SaveChangesAsync(cancellationToken);
                await CommitTransactionsAsync();
                IsCompleted = true;
                await OnCompletedAsync();
            }
            catch (Exception ex)
            {
                _exception = ex;

                if (Options.ThrowWhenExpection)
                {
                    throw;
                }
                //throw;
            }
        }

        protected virtual async Task OnCompletedAsync()
        {
            foreach (var handler in CompletedHandlers)
            {
                await handler.Invoke();
            }
        }
        protected virtual async Task CommitTransactionsAsync(CancellationToken cancellationToken = default)
        {
            foreach (var transaction in GetAllActiveTransactions())
            {
                await transaction.CommitAsync(cancellationToken);
            }
        }
        public IReadOnlyList<ITransactionApi> GetAllActiveTransactions()
        {
            return _transactions.Values.ToImmutableList();
        }
        private void PreventMultipleComplete()
        {
            if (IsCompleted || _isCompleting)
            {
                throw new Exception("Complete is called before!");
            }
        }

        public void Initialize(UnitOfWorkOption options)
        {
            Options = options;
        }

        public void OnCompleted(Func<Task> handler)
        {
            CompletedHandlers.Add(handler);
        }

        public async Task RollbackAsync(CancellationToken cancellationToken = default)
        {
            if (_isRolledback)
            {
                return;
            }

            _isRolledback = true;

            await RollbackAllAsync(cancellationToken);
        }
        protected virtual async Task RollbackAllAsync(CancellationToken cancellationToken)
        {
            //foreach (var dbContext in GetAllActiveDatabaseApis())
            //{
            //    try
            //    {
            //        await dbContext.Database.RollbackTransactionAsync(cancellationToken);
            //    }
            //    catch { 
            //    }
            //}
            foreach (var transactionApi in GetAllActiveTransactions())
            {
                try
                {
                    await transactionApi.RollbackAsync(cancellationToken);
                }
                catch { }
            }
        }
        public async Task SaveChangesAsync(CancellationToken cancellationToken = default)
        {
            foreach (var dbContext in GetAllActiveDatabases())
            {
                await dbContext.SaveChangesAsync(cancellationToken);
            }
        }
        public IReadOnlyList<IDbContextApi> GetAllActiveDatabases()
        {
            return _dbContexts.Values.ToImmutableList();
        }
        public virtual void SetOuter(IZFYUnitOfWork outer)
        {
            Outer = outer;
        }


        public ITransactionApi FindDbTransaction(string key)
        {
            
            return _transactions.GetValueOrDefault(key);
        }

        public void AddDbTransaction(string key, ITransactionApi api)
        {
           

            if (_transactions.ContainsKey(key))
            {
                throw new InvalidOperationException("There is already a transaction API in this unit of work with given key: " + key);
            }

            _transactions.Add(key, api);
        }

        public IDbContextApi FindDbContext(string key)
        {
            return _dbContexts.GetValueOrDefault(key);
        }

        public void AddDbContext(string key, IDbContextApi api)
        {
            

            if (_dbContexts.ContainsKey(key))
            {
                throw new InvalidOperationException("There is already a database API in this unit of work with given key: " + key);
            }

            _dbContexts.Add(key, api);
        }

        public virtual void Dispose()
        {
            if (IsDisposed)
            {
                return;
            }

            IsDisposed = true;

            DisposeTransactions();
            if (!IsCompleted || _exception != null)
            {
                OnFailed();
            }

            OnDisposed();
        }
        protected virtual void OnFailed()
        {
            Failed.InvokeSafely(this, new UnitOfWorkFailedEventArgs(this, _exception, _isRolledback));
        }

        protected virtual void OnDisposed()
        {
            Disposed.InvokeSafely(this, new UnitOfWorkEventArgs(this));
        }
        private void DisposeTransactions()
        {
            foreach (var transactionApi in GetAllActiveTransactions())
            {
                try
                {
                    transactionApi.Dispose();
                }
                catch
                {
                }
            }
        }
    }
}
